
package stratego.java.ssm.gui;

import java.awt.AlphaComposite;
import java.awt.Canvas;
import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.GraphicsConfiguration;
import java.awt.GraphicsEnvironment;
import java.awt.Image;
import java.awt.Point;
import java.awt.RenderingHints;
import java.awt.event.InputEvent;
import java.awt.event.MouseEvent;
import java.awt.image.BufferStrategy;
import java.awt.image.BufferedImage;
import java.awt.image.VolatileImage;
import java.io.File;
import java.io.IOException;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.Map;
import java.util.concurrent.atomic.AtomicBoolean;

import javax.imageio.ImageIO;
import javax.swing.event.MouseInputListener;

import stratego.java.ssm.Board;
import stratego.java.ssm.Stratego;
import stratego.java.ssm.piece.Piece;
import stratego.java.ssm.piece.PieceUtilities;

/**
 * <code>public class GameCanvas extends Canvas implements Runnable, MouseInputListener</code>
 * <p>
 * This class is the GUI presented to the user in game. It allows the user to interact with the
 * board, but is limited to showing the pieces on the board, and reporting where the user clicked.
 * </p>
 * 
 * @author Mark Aligbe
 * 
 */
public class GameCanvas extends Canvas implements Runnable, MouseInputListener
{
	private static final long	           serialVersionUID	= 1234352996180268379L;
	// How long the last frame took to render
	private long	                       cycleTime, startCycleTime;
	// In order: if the mouse is above the canvas (and that the application has focus) and the
	// direction of the highlighted square.
	private boolean	                       growing, firstRun = true;
	/**
	 * Reports whether or not this canvas is rendering. If it is not rendering and it needs to be
	 * started, this rendering must be set to true and this object must be notified
	 */
	public AtomicBoolean	               isRunning;
	/**
	 * Reports whether or not the GameCanvas is currently in selection mode. In this mode, the
	 * pieces that the the listeners receive is different from the current piece on the board.
	 */
	public AtomicBoolean	               isSelecting;
	// Some other variables that significantly affect the behavior of the board rendering.
	// mouseInside isn't used as much as I though I would use it, isFlipping is similar to the last
	// 3 atomic booleans, and isAnimating is a general variable that indicates some sort of
	// animation is inProgress.
	private AtomicBoolean	               mouseInside, isFlipping, isAnimating;
	// Doubles representing the scale of the grid.
	private double	                       horizontalSeparation, verticalSeparation;
	// In order: the pixel width of the grid lines, the max size change of the highlighted square,
	// the current x-coordinate of the highlighted square, the current y-coordinate of the current
	// square, and the current size change of the highlighted square.
	private int	                           thickness	    = 12, maxGrowth = 10, gridX, gridY,
	        growth, lastHeight, lastWidth;
	private long	                       gradCount;
	// The board to render
	private volatile Board	               board;
	// The piece selector to work with
	private PieceSelector	               selector;
	// The two colors for the gradient calculation
	private Color	                       g1	            = new Color(149, 170, 191);
	private Color	                       g2	            = new Color(84, 112, 146);
	// The background image to tile and the tiled background
	private VolatileImage	               bg, tbg;
	// The registered watchers of this object
	private LinkedList<BoardEventListener>	watchers;
	// The pieces to be currently animated
	private HashMap<Point, Piece>	       animPiece;
	private Object	                       lock;
	// Some instance variables for flipping a piece
	private final double	               maxRotate	    = Math.PI / 2.0;
	private final int	                   maxSteps	        = 15;
	private int	                           step	            = 1;
	private double	                       radian	        = 0;
	public AtomicBoolean	               inGame;
	// A HashSet containing the location of the two pieces currently being selected by the players
	private HashMap<Point, Color>	       highlightPoints;
	// An AtomicBoolean indicating this game canvas should be highlighting the points in the above
	// HashMap, or moving them, or destroying them.
	private AtomicBoolean	               isHighlighting, isMoving, isDestroying;
	
	/**
	 * <code>public GameCanvas()</code>
	 * <p>
	 * Constructs a new GameCanvas with no specific GraphicsConfiguration. Waits to be started
	 * before beginning rendering. The board must be initialized by a call to addBoard(b) before
	 * running this Runnable.
	 * </p>
	 */
	public GameCanvas()
	{
		super();
		setBackground(Color.WHITE);
		isRunning = new AtomicBoolean(true);
		isSelecting = new AtomicBoolean(false);
		addMouseMotionListener(this);
		addMouseListener(this);
		// Set up the background image
		BufferedImage img = null;
		try
		{
			File folder = new File("img");
			img = ImageIO.read(new File(folder, "bg.jpg"));
		}
		catch (IOException e)
		{
			e.printStackTrace();
		}
		bg =
		        GraphicsEnvironment
		                .getLocalGraphicsEnvironment()
		                .getDefaultScreenDevice()
		                .getDefaultConfiguration()
		                .createCompatibleVolatileImage(img.getWidth(), img.getHeight(),
		                        img.getTransparency());
		bg.getGraphics().drawImage(img, 0, 0, null);
		// Initialize all the initializable variables (variables not initialized by other classes).
		mouseInside = new AtomicBoolean(false);
		isFlipping = new AtomicBoolean(false);
		isAnimating = new AtomicBoolean(false);
		inGame = new AtomicBoolean(false);
		isHighlighting = new AtomicBoolean(false);
		isMoving = new AtomicBoolean(false);
		isDestroying = new AtomicBoolean(false);
		animPiece = new HashMap<Point, Piece>();
		highlightPoints = new HashMap<Point, Color>();
		watchers = new LinkedList<BoardEventListener>();
	}
	
	/**
	 * <code>public GameCanvas(GraphicsConfiguration arg0)</code>
	 * <p>
	 * Constructs a new GameCanvas with the specified GraphicsConfiguration. Waits to be started
	 * before beginning rendering. The board must be initialized by a call to addBoard(b) before
	 * running this Runnable.
	 * </p>
	 * 
	 * @param arg0
	 *            - The specific GraphicsConfiguration to use.
	 */
	public GameCanvas(GraphicsConfiguration arg0)
	{
		super(arg0);
		setBackground(Color.WHITE);
		isRunning = new AtomicBoolean(true);
		addMouseMotionListener(this);
		addMouseListener(this);
		// Set up the background image
		BufferedImage img = null;
		try
		{
			// A cross platform way to get to the desired image. Unix uses \, whereas Windows
			// uses /
			File folder = new File("img");
			File file = new File(folder, "IMG11.jpg");
			img = ImageIO.read(file);
		}
		catch (IOException e)
		{
			e.printStackTrace();
		}
		bg =
		        arg0.createCompatibleVolatileImage(img.getWidth(), img.getHeight(),
		                img.getTransparency());
		bg.getGraphics().drawImage(img, 0, 0, null);
		mouseInside = new AtomicBoolean(false);
		watchers = new LinkedList<BoardEventListener>();
		isFlipping = new AtomicBoolean(false);
		isAnimating = new AtomicBoolean(false);
		inGame = new AtomicBoolean(false);
		isHighlighting = new AtomicBoolean(false);
		isMoving = new AtomicBoolean(false);
		isDestroying = new AtomicBoolean(false);
		highlightPoints = new HashMap<Point, Color>();
		animPiece = new HashMap<Point, Piece>();
	}
	
	/**
	 * <code>public void addBoard(Board b)</code>
	 * <p>
	 * Gives the GameCanvas access to a board object. Should be the same board as used in
	 * Offline/OnlineGame.
	 * </p>
	 * 
	 * @param b
	 */
	public void addBoard(Board b)
	{
		this.board = b;
	}
	
	/**
	 * <code>public void setSelector(PieceSelector p)</code>
	 * <p>
	 * Sets the current selector to read from when this canvas is in Piece selecting mode.
	 * </p>
	 * 
	 * @param p
	 *            - The PieceSelector with the correct gradients for the pieces set.
	 */
	public void setSelector(PieceSelector p)
	{
		this.selector = p;
	}
	
	@Override
	/**The rendering logic of this canvas.*/
	public void run()
	{
		cycleTime = System.currentTimeMillis();
		createBufferStrategy(2);
		BufferStrategy strategy = getBufferStrategy();
		// The rendering pipeline
		while (true)
		{
			if (isRunning.get())
			{
				// Update the startCycleTime
				startCycleTime = System.currentTimeMillis();
				// Determine the new horizontal and vertical separation
				horizontalSeparation = (double) getWidth() / board.getBoard().length;
				verticalSeparation = (double) getHeight() / board.getBoard().length;
				// Get the graphics object to draw images to
				Graphics2D g = (Graphics2D) strategy.getDrawGraphics();
				g.setRenderingHint(RenderingHints.KEY_RENDERING,
				        RenderingHints.VALUE_RENDER_QUALITY);
				g.setRenderingHint(RenderingHints.KEY_INTERPOLATION,
				        RenderingHints.VALUE_INTERPOLATION_BILINEAR);
				g.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
				        RenderingHints.VALUE_ANTIALIAS_ON);
				// Draw the background
				drawBackground(g);
				// Draw the grid
				drawGrid(g);
				// Draw the board pieces
				drawPieces(g);
				// Another addition to the rendering pipeline, to support animations.
				if (isAnimating.get())
				{
					if (isFlipping.get())
						flipPieces(g, animPiece, lock);
					if (isMoving.get())
						movePieces(g, animPiece, lock);
					if (isDestroying.get())
						destroyPieces(g, animPiece, lock);
				}
				// If the GameCanvas is in selection mode, highlight the region of validity and show
				// the piece currently selected by the PieceSelector.
				if (isSelecting.get())
				{
					highlightValidRegion(g);
					showSelectedPiece(g);
				}
				// Highlight the selected square if the canvas is not in selection mode
				else if (mouseInside.get())
				{
					highlightSquare(g);
					// Reset the gradient count for the highlightValid region otherwise.
					gradCount = 0L;
					// System.out.println("(" + gridX + "," + gridY + ")");
				}
				// If a piece has been a selected in normal game mode, overlay the desired color on
				// top of the piece.
				if (isHighlighting.get())
					highlightPieces(g);
				// Updating various variables
				lastWidth = getWidth();
				lastHeight = getHeight();
				if (firstRun)
					firstRun = false;
				// Show the current frame
				g.dispose();
				strategy.show();
				synchFramerate();
			}
			else
			{
				synchronized (this)
				{
					try
					{
						wait();
					}
					catch (InterruptedException e)
					{
						e.printStackTrace();
					}
				}
			}
		}
	}
	
	/**
	 * <code>private void destroyPieces(Graphics2D g, HashMap&ltPoint, Piece&gt animPiece, Object lock)</code>
	 * <p>
	 * Makes the pieces appear as though they are shrinking into nothingness. Occurs during a
	 * regular frame update.
	 * </p>
	 * 
	 * @param g
	 *            - The Graphics2D object to draw to.
	 * @param animPiece
	 *            - The HashMap&ltPoint, Piece&gt to get the pieces from.
	 * @param lock
	 *            - The Object to notify once completed.
	 */
	private void destroyPieces(Graphics2D g, HashMap<Point, Piece> animPiece, Object lock)
	{
		// The width and height should be the current fraction of the width
		int width = (int) Math.round(horizontalSeparation * (maxSteps - step) / (double) maxSteps);
		int height = (int) Math.round(verticalSeparation * (maxSteps - step) / (double) maxSteps);
		// Shift the piece over by half of the space between the piece's image and the grid edge.
		int xShift = (int) Math.round(horizontalSeparation - width) / 2;
		int yShift = (int) Math.round(verticalSeparation - height) / 2;
		// Shrink each piece being animated
		for (Map.Entry<Point, Piece> entry : animPiece.entrySet())
		{
			int x = (int) Math.round(entry.getKey().x * horizontalSeparation);
			int y = (int) Math.round(entry.getKey().y * verticalSeparation);
			g.drawImage(entry.getValue().getImage(), x + xShift, y + yShift, width, height, null);
		}
		// Go to the next step
		step++;
		// We're done
		if (step == maxSteps)
		{
			isAnimating.set(false);
			isDestroying.set(false);
			step = 1;
			synchronized (lock)
			{
				lock.notify();
			}
		}
	}
	
	/**
	 * <code>public void destroyPieces(HashMap&ltPoint, Piece&gt pieces, Object lock)</code>
	 * <p>
	 * A helper method to tell the canvas to destroy the specified pieces. It is up to the caller to
	 * actually remove them from the board.
	 * </p>
	 * 
	 * @param pieces
	 *            - The HashMap&ltPoint, Piece&gt containing the pieces to destroy
	 * @param lock
	 *            - The lock to notify once completed.
	 */
	public void destroyPieces(HashMap<Point, Piece> pieces, Object lock)
	{
		this.lock = lock;
		animPiece = pieces;
		isAnimating.set(true);
		isDestroying.set(true);
	}
	
	/**
	 * <code>public void movePiece(Piece piece, Piece empty, Point oldLoc, Point newLoc, Object lock)</code>
	 * <p>
	 * A helper method to tell the canvas to move the specified Piece to the specified location. The
	 * Piece at the destination should be an EmptyPiece.</code>
	 * 
	 * @param piece
	 *            - The Piece to animate.
	 * @param empty
	 *            - The Piece at the destination location.
	 * @param oldLoc
	 *            - The location of the Piece that will be moving.
	 * @param newLoc
	 *            - The destination of the moving Piece.
	 * @param lock
	 *            - The object to notify once completed.
	 */
	public void movePiece(Piece piece, Piece empty, Point oldLoc, Point newLoc, Object lock)
	{
		this.lock = lock;
		animPiece = new HashMap<Point, Piece>();
		animPiece.put(oldLoc, piece);
		animPiece.put(newLoc, empty);
		isAnimating.set(true);
		isMoving.set(true);
	}
	
	/**
	 * <code>private void movePieces(Graphics2D g, HashMap&ltPoint, Piece&gt animPiece, Object lock)</code>
	 * <p>
	 * Moves some non-empty piece to some other location.
	 * </p>
	 * 
	 * @param g
	 *            - The Graphics2D object to draw to.
	 * @param animPiece
	 *            - A HashMap&ltPoint, Piece&gt containing only 2 pieces to be animated.
	 * @param lock
	 *            - The Object to notify once done.
	 */
	private void movePieces(Graphics2D g, HashMap<Point, Piece> animPiece, Object lock)
	{
		Piece p = null;
		Point src = null, dest = null;
		// animPiece should only have a size of 2 while this method is running
		for (Map.Entry<Point, Piece> entry : animPiece.entrySet())
		{
			if (p == null && !entry.getValue().isEmpty())
				p = entry.getValue();
			if (src == null && !entry.getValue().isEmpty())
				src = entry.getKey();
			else
				dest = entry.getKey();
		}
		double rate = 1.5;
		// The width and height of the picture
		int width = (int) Math.round(horizontalSeparation);
		int height = (int) Math.round(verticalSeparation);
		// How much the picture should be drawn from its original location.
		int xShift =
		        (int) Math
		                .round((src.x - dest.x) * horizontalSeparation / (maxSteps * rate) * step);
		int yShift =
		        (int) Math.round((src.y - dest.y) * verticalSeparation / (maxSteps * rate) * step);
		g.drawImage(p.getImage(), (int) Math.round(src.x * horizontalSeparation) - xShift,
		        (int) Math.round(src.y * verticalSeparation) - yShift, width, height, null);
		step++;
		// We're done
		if (step >= maxSteps * rate)
		{
			isAnimating.set(false);
			isMoving.set(false);
			step = 1;
			synchronized (lock)
			{
				lock.notify();
			}
		}
	}
	
	/**
	 * <code>private void highlightPieces(Graphics2D g)</code>
	 * <p>
	 * A simple piece of code to draw some color over a specified grid contained in a
	 * HashMap&ltPoint, Color&gt. Ideally, the color should be transparent.
	 * </p>
	 * 
	 * @param g
	 *            - The Graphics2D object to draw to.
	 */
	private void highlightPieces(Graphics2D g)
	{
		// We don't really need the entrySet to be persistent or local
		synchronized (highlightPoints)
		{
			for (Map.Entry<Point, Color> entrySet : highlightPoints.entrySet())
			{
				g.setColor(entrySet.getValue());
				Point p = entrySet.getKey();
				g.fillRect((int) Math.round(p.x * horizontalSeparation),
				        (int) Math.round(p.y * verticalSeparation),
				        (int) Math.round(horizontalSeparation),
				        (int) Math.round(verticalSeparation));
			}
		}
	}
	
	/**
	 * <code>public void highlightLocation(Point loc, Color color)</code>
	 * <p>
	 * A helper method for adding points to be highlighted.
	 * </p>
	 * 
	 * @param loc
	 *            - A Point representing the grid square to be highlighted.
	 * @param color
	 *            - The color to lay on top of the grid.
	 */
	public void highlightLocation(Point loc, Color color)
	{
		synchronized (highlightPoints)
		{
			highlightPoints.put(loc, color);
		}
		if (!isHighlighting.get())
			isHighlighting.set(true);
	}
	
	/**
	 * <code>public void clearHighlightLocation()</code>
	 * <p>
	 * A helper method to clear the points currently being highlighted.
	 * </p>
	 */
	public void clearHighlightLocation()
	{
		synchronized (highlightPoints)
		{
			highlightPoints.clear();
		}
		isHighlighting.set(false);
	}
	
	/**
	 * <code>private void flipPieces(Graphics2D g, HashMap&ltPoint, Piece&gt animPiece, Object lock)</code>
	 * <p>
	 * An addition to the rendering pipeline to flip the pieces contained in a HashMap&ltPoint,
	 * Piece&gt onto their other side.
	 * </p>
	 * 
	 * @param g
	 *            - The Graphics2D Object to draw to.
	 * @param animPiece
	 *            - A HashMap&ltPoint, Piece&gt containing the pieces to animate.
	 * @param lock
	 *            - The object to notify once completed.
	 */
	private void flipPieces(Graphics2D g, HashMap<Point, Piece> animPiece, Object lock)
	{
		// Flip over each piece
		for (Map.Entry<Point, Piece> entry : animPiece.entrySet())
		{
			// Get the current piece and location (to make the code width shorter)
			Piece piece = entry.getValue();
			Point point = entry.getKey();
			// Was initially more, but still useful.
			int cellWidth = (int) Math.round(horizontalSeparation);
			// The next width the draw the picture as
			int width =
			        (int) Math.abs(Math.round((horizontalSeparation - thickness + 1)
			                * Math.cos(radian)));
			// The height of the picture.
			int height = (int) Math.round(verticalSeparation) - thickness + 1;
			// The x to draw it at
			int x = (int) Math.round(point.x * horizontalSeparation);
			// Shift it by the current difference in its normal size and it's rotated size
			if (Math.cos(radian) < 0)
				x +=
				        (int) Math
				                .round((cellWidth - Math.abs(cellWidth * Math.cos(radian))) / 2.0) + 6;
			else
				x +=
				        (int) Math
				                .round((cellWidth - Math.abs(cellWidth * Math.cos(radian))) / 2.0) + 6;
			// The height is the normal height
			int y = (int) Math.round(point.y * verticalSeparation + thickness / 2.0);
			// Determine which picture to draw depending on which picture was initially showing. If
			// the front of the picture was selected, then it should be drawn while we're still on
			// the positive side of the rotation.
			if (!piece.backShowing.get())
			{
				if (Math.cos(radian) < 0)
					g.drawImage(piece.getBack(), x, y, width, height, null);
				else
					g.drawImage(piece.getImage(), x, y, width, height, null);
			}
			else
			{
				if (Math.cos(radian) > 0)
					g.drawImage(piece.getBack(), x, y, width, height, null);
				else
					g.drawImage(piece.getImage(), x, y, width, height, null);
			}
		}
		// Increment radian and step and wait for the next draw cycle.
		radian = maxRotate / maxSteps * step++;
		// We're done
		if (step == maxSteps * 2 + 1)
		{
			// Flip the state of the pieces each method call
			for (Map.Entry<Point, Piece> entry : animPiece.entrySet())
				entry.getValue().backShowing.set(!entry.getValue().backShowing.get());
			isFlipping.set(false);
			isAnimating.set(false);
			radian = 0;
			step = 1;
			synchronized (lock)
			{
				lock.notify();
			}
		}
	}
	
	/**
	 * <code>public void flipPieces(HashMap&ltPoint, Piece&gt pieceLocations, Object lock)</code>
	 * <p>
	 * A helper method to animate flipping the pieces specified in the given HashMap&ltPoint,
	 * Piece&gt.
	 * 
	 * @param pieceLocations
	 *            - A HashMap&ltPoint, Piece&gt containing the pieces to be flipped.
	 * @param lock
	 *            - The object to notify once done.
	 */
	public void flipPieces(HashMap<Point, Piece> pieceLocations, Object lock)
	{
		this.lock = lock;
		isFlipping.set(true);
		isAnimating.set(true);
		animPiece = pieceLocations;
	}
	
	/**
	 * <code>private void showSelectedPiece(Graphics2D g)</code>
	 * <p>
	 * Works with the PieceSelector to display the current piece chosen by the player in the grid
	 * the mouse is currently in.
	 * </p>
	 * 
	 * @param g
	 *            - The Graphics2D object to draw to.
	 */
	private void showSelectedPiece(Graphics2D g)
	{
		Piece p = selector.getSelectedPiece();
		if (p == null)
			return;
		g.drawImage(p.getImage(), (int) Math.round(gridX * horizontalSeparation + thickness / 2.0),
		        (int) Math.round(gridY * verticalSeparation + thickness / 2.0),
		        (int) Math.round(horizontalSeparation) - thickness + 1,
		        (int) Math.round(verticalSeparation) - thickness + 1, null);
	}
	
	/**
	 * <code>private void highlightValidRegion(Graphics2D g)</code>
	 * <p>
	 * Displays a rectangle showing the valid region that a player may place pieces. Typically, it
	 * is just their half-1-tall side of the board. Blocks are understood not to be placeable.
	 * </p>
	 * 
	 * @param g
	 *            - The Graphics2D object to draw to.
	 */
	private void highlightValidRegion(Graphics2D g)
	{
		// Determine the player in order to determine which half of the board to draw to.
		int player = selector.getPlayer();
		// Set up the size of the image, which is independent of whichever side they choose
		int width = getWidth();
		// The height (in a default board) is 4 grid squares. The height of a grid square is the
		// verticalSeparation, and each player gets half of the board except for the center two rows
		int height = (int) Math.round(verticalSeparation * (board.getBoard().length / 2 - 1));
		VolatileImage alpha =
		        getGraphicsConfiguration().createCompatibleVolatileImage(width, height,
		                VolatileImage.TRANSLUCENT);
		// The color gradient to use depending on the player
		Color[] gradient;
		// Highlight the bottom half of the board
		if (player == 1)
			gradient = PieceUtilities.redGradient;
		// Highlight the top half of the board
		else
			gradient = PieceUtilities.blueGradient;
		Color start = gradient[0];
		start = new Color(start.getRed(), start.getGreen(), start.getBlue());
		Color end = gradient[1];
		end = new Color(end.getRed(), end.getGreen(), end.getBlue());
		// Get the graphics for the image
		Graphics2D alphaG = alpha.createGraphics();
		// Clear the image
		alphaG.setComposite(AlphaComposite.Clear);
		alphaG.fillRect(0, 0, alpha.getWidth(), alpha.getHeight());
		// Draw the transparent image
		alphaG.setComposite(AlphaComposite.SrcOver);
		int[] shift =
		        new int[] {end.getRed() - start.getRed(), end.getGreen() - start.getGreen(),
		                end.getBlue() - start.getBlue()};
		double angVel = Math.PI * 6;
		alphaG.setColor(new Color(start.getRed()
		        + (int) Math.round(shift[0] * Math.sin(gradCount / angVel)), start.getGreen()
		        + (int) Math.round(shift[1] * Math.sin(gradCount / angVel)), start.getBlue()
		        + (int) Math.round(shift[2] * Math.sin(gradCount / angVel)), 63));
		gradCount++;
		alphaG.fillRect(0, 0, alpha.getWidth(), alpha.getHeight());
		int y = 0;
		if (player == 1)
			y = (int) Math.round(verticalSeparation * (board.getBoard().length / 2 + 1));
		g.drawImage(alpha, 0, y, null);
	}
	
	/**
	 * <code>private void drawBackground(Graphics g)</code>
	 * <p>
	 * Tiles the given background image to the dimension of this GameCanvas object.
	 * </p>
	 * 
	 * @param g
	 *            - The Graphics object to draw to.
	 */
	private void drawBackground(Graphics2D g)
	{
		// Permits resizing (avoids resizing on the first initialization)
		if (tbg == null || (!firstRun && (lastWidth != getWidth() || lastHeight != getHeight())))
		{
			tbg =
			        getGraphicsConfiguration().createCompatibleVolatileImage(getWidth(),
			                getHeight(), VolatileImage.OPAQUE);
			for (int x = 0; x < getWidth(); x++)
			{
				for (int y = 0; y < getHeight(); y++)
				{
					tbg.getGraphics().drawImage(bg, x, y, null);
					y += bg.getHeight() - 1;
				}
				x += bg.getWidth() - 1;
			}
		}
		g.drawImage(tbg, 0, 0, getWidth(), getHeight(), null);
	}
	
	/**
	 * <code>private void drawPieces(Graphics g)</code>
	 * <p>
	 * Draws the pieces on the board according to a predetermined set of rules.
	 * </p>
	 * 
	 * @param g
	 *            - The Graphics object to draw to.
	 */
	private void drawPieces(Graphics2D g)
	{
		for (int y = board.getBoard().length - 1; y >= 0; y--)
		{
			for (int x = 0; x < board.getBoard().length; x++)
			{
				if (isAnimating.get())
					if (animPiece.containsKey(new Point(x, y)))
						continue;
				Image img = null;
				if (!board.getBoard()[x][y].backShowing.get())
					img = board.getBoard()[x][y].getImage();
				else
					img = board.getBoard()[x][y].getBack();
				if (img == null)
					continue;
				if (gridX == x && gridY == y && !board.getBoard()[x][y].isBlock()
				        && !board.getBoard()[x][y].isEmpty())
				{
					int shift = Math.round(growth / 2);
					g.drawImage(img, (int) Math.round(gridX * horizontalSeparation) - shift,
					        (int) Math.round(gridY * verticalSeparation) - shift,
					        (int) Math.round(horizontalSeparation) + growth,
					        (int) Math.round(verticalSeparation) + growth, null);
					// Changes growth according to whether it should be growing or shrinking
					if (growing)
						growth++;
					else
						growth--;
					// Switches the direction of growth once it reaches maxGrowth
					if (Math.abs(growth) == maxGrowth)
					{
						if (growing)
							growing = false;
						else
							growing = true;
					}
				}
				else
					// What's up with the 1 pixel error?
					g.drawImage(img, (int) Math.round(x * horizontalSeparation + thickness / 2.0),
					        (int) Math.round(y * verticalSeparation + thickness / 2.0),
					        (int) Math.round(horizontalSeparation) - thickness + 1,
					        (int) Math.round(verticalSeparation) - thickness + 1, null);
			}
		}
	}
	
	/**
	 * <code>private void highlightSquare(Graphics g)</code>
	 * <p>
	 * Highlights the current selected grid in a greed square that grows and shrinks
	 * </p>
	 * 
	 * @param g
	 *            - The Graphics object to draw to.
	 */
	private void highlightSquare(Graphics2D g)
	{
		try
		{
			if (!board.getBoard()[gridX][gridY].isEmpty())
				return;
		}
		// A rare bug on startup
		catch (IndexOutOfBoundsException e)
		{
			return;
		}
		g.setColor(new Color(0, 0, 255, 128));
		// The amount to shift the square to keep it cocentric with the grid square
		int shift = Math.round(growth / 2);
		// Draws the square at the location of the grid with the same size as the grid plus some
		// growth factor
		g.fillRect((int) Math.round(gridX * horizontalSeparation) - shift,
		        (int) Math.round(gridY * verticalSeparation) - shift,
		        (int) Math.round(horizontalSeparation) + growth + 1,
		        (int) Math.round(verticalSeparation) + growth + 1);
		// Changes growth according to whether it should be growing or shrinking
		if (growing)
			growth++;
		else
			growth--;
		// Switches the direction of growth once it reaches maxGrowth
		if (Math.abs(growth) == maxGrowth)
		{
			if (growing)
				growing = false;
			else
				growing = true;
		}
	}
	
	/**
	 * <code>private void drawGrid(Graphics g)</code>
	 * <p>
	 * Draws the grid to the canvas. Currently trying to make the grid more pretty.
	 * </p>
	 * 
	 * @param g
	 *            - The Graphics object to draw the grid to.
	 */
	private void drawGrid(Graphics2D g)
	{
		// Draw a line for each column/row - 2
		for (int i = 1; i < board.getBoard().length; i++)
		{
			int pos = -(int) Math.round(thickness / 2.0);
			for (int j = 0; j <= thickness; j++)
			{
				// Outline it in a dark color
				if (j == 0)
					g.setColor(new Color(83, 105, 132));
				else if (j == thickness)
					g.setColor(new Color(49, 78, 113));
				else
				{
					g.setColor(new Color(g1.getRed()
					        - (int) Math.round((g1.getRed() - g2.getRed()) / ((double) thickness)
					                * j), g1.getGreen()
					        - (int) Math.round((g1.getGreen() - g2.getGreen())
					                / ((double) thickness) * j), g1.getBlue()
					        - (int) Math.round((g1.getBlue() - g2.getBlue()) / ((double) thickness)
					                * j)));
				}
				// Draw the horizontal lines
				g.drawLine(0, (int) Math.round(i * verticalSeparation) + pos, getWidth(),
				        (int) Math.round(i * verticalSeparation) + pos);
				// Draw the vertical lines
				g.drawLine((int) Math.round(i * horizontalSeparation) + pos, 0,
				        (int) Math.round(i * horizontalSeparation) + pos, getHeight());
				pos++;
			}
		}
	}
	
	/**
	 * <code>private void synchFramerate()</code>
	 * <p>
	 * Syncs the frame rate so that it is no faster than 1/FRAME_DELAY.
	 * </p>
	 */
	private void synchFramerate()
	{
		cycleTime = System.currentTimeMillis();
		long difference = Math.round(Stratego.FRAME_DELAY - (cycleTime - startCycleTime));
		if (difference > 0)
		{
			try
			{
				Thread.sleep(difference);
			}
			catch (InterruptedException e)
			{
				e.printStackTrace();
			}
		}
	}
	
	/**
	 * <code>public synchronized void registerBoardListener(BoardEventListener listener)</code>
	 * <p>
	 * Adds some object to the list of listeners for this class. The objects registered with this
	 * class will be notified of user input.
	 * </p>
	 * 
	 * @param listener
	 */
	public synchronized void registerBoardListener(BoardEventListener listener)
	{
		watchers.add(listener);
	}
	
	/**
	 * <code>public synchronized void removeBoardListener(BoardEventListener listener)</code>
	 * <p>
	 * Removes some board listener from the list of listeners. Relies on each object's equals
	 * method.
	 * </p>
	 * 
	 * @param listener
	 */
	public synchronized void removeBoardListener(BoardEventListener listener)
	{
		watchers.remove(listener);
	}
	
	@Override
	public void mouseClicked(MouseEvent e)
	{}
	
	@Override
	/** Records when the mouse has entered the canvas */
	public void mouseEntered(MouseEvent e)
	{
		if (!mouseInside.get())
			mouseInside.set(true);
	}
	
	@Override
	/**Records when the mouse has left the canvas*/
	public void mouseExited(MouseEvent e)
	{
		if (mouseInside.get())
			mouseInside.set(false);
	}
	
	@Override
	public void mousePressed(MouseEvent e)
	{}
	
	@Override
	/**Fires a boardClicked event in each of this object's watchers in a separate thread*/
	public synchronized void mouseReleased(MouseEvent e)
	{
		// Another bug fix
		if (board == null)
			return;
		// Yet another bug fix
		if (gridX >= board.getBoard().length || gridY >= board.getBoard().length)
			return;
		// Don't fire an event while animating
		if (isAnimating.get())
			return;
		// The necessity of the event to be final for an anonymous class declaration requires
		// writing two very similar conditionals
		if (!isSelecting.get() || e.getModifiers() == InputEvent.BUTTON3_MASK)
		{
			final BoardEvent event =
			        new BoardEvent(this, board, gridX, gridY, e.getModifiers(),
			                board.getBoard()[gridX][gridY]);
			for (final BoardEventListener listener : watchers)
			{
				(new Thread(new Runnable()
				{
					@Override
					public void run()
					{
						listener.boardClicked(event);
					}
				})).start();
			}
		}
		// If the canvas is selecting, notify the listeners of the current grid that was clicked and
		// the piece the selecter represents.
		else
		{
			if (selector.getSelectedPiece() == null)
				return;
			final BoardEvent event =
			        new BoardEvent(this, board, gridX, gridY, e.getModifiers(),
			                selector.getSelectedPiece());
			for (final BoardEventListener listener : watchers)
			{
				(new Thread(new Runnable()
				{
					@Override
					public void run()
					{
						listener.boardClicked(event);
					}
				})).start();
			}
		}
	}
	
	@Override
	/**Changes the current grid as the mouse as dragged from grid to grid*/
	public void mouseDragged(MouseEvent e)
	{
		Point p = e.getPoint();
		gridX = (int) Math.floor(p.getX() / horizontalSeparation);
		gridY = (int) Math.floor(p.getY() / verticalSeparation);
	}
	
	@Override
	/**Records the current grid square that the mouse is over*/
	public void mouseMoved(MouseEvent e)
	{
		Point p = e.getPoint();
		gridX = (int) Math.floor(p.getX() / horizontalSeparation);
		gridY = (int) Math.floor(p.getY() / verticalSeparation);
	}
}
